// ============================== 导入依赖 ============================== //

import { checkDebug } from './log'
import IS from './is'

// ============================== 导出 GRPC 工具 ============================== //

export default {
  /**
   * GRPC 请求
   *
   * @param {String} TIP    提示语
   * @param {Object} CLIENT 客户端
   * @param {String} method 方法名
   * @param {Object} DTO    请求体
   * @param {Object} meta   请求头
   *
   * @returns {Object} resp 响应体
   */
  async grpc(TIP, CLIENT, method, DTO, meta) {
    const beginTime = Date.now() // 开始时间戳
    try {
      const name = transMethod(TIP, CLIENT, method, DTO, this) // 转换方法名
      const RESP = await CLIENT[name](DTO, meta || this.GRPC.META) // 远程调用
      return success(TIP, DTO, RESP, this, beginTime) // 返回成功
    } catch (err) {
      throw error(TIP, DTO, err, this, beginTime) // 抛出错误
    }
  },

  /**
   * GRPC 下载
   *
   * @param {String} TIP      提示语
   * @param {Object} CLIENT   客户端
   * @param {String} method   方法名
   * @param {Object} DTO      请求体
   * @param {Object} meta     请求头
   * @param {Object} filename 文件名 (为空则取返回体中的文件名 resp.fileName)
   *
   * @returns {Object} resp   响应体
   */
  async grpcDownload(TIP, CLIENT, method, DTO, meta, filename) {
    const beginTime = Date.now() // 开始时间戳
    try {
      const name = transMethod(TIP, CLIENT, method, DTO, this) // 转换方法名
      const RESP = await CLIENT[name](DTO, meta || this.GRPC.META) // 远程调用
      const resp = success(TIP, DTO, RESP, this, beginTime) // 响应成功
      saveFile(TIP, DTO, resp, this, beginTime, filename) // 保存文件
    } catch (err) {
      throw error(TIP, DTO, err, this, beginTime) // 抛出错误
    }
  },

  /**
   * GRPC 客户端
   *
   * @param {Function} Client 客户端类
   * @param {Object}   url    接口路径
   *
   * @returns {Object} CLIENT 客户端
   */
  grpcCLIENT(Client, url) {
    const CLIENT = new Client()
    if (CLIENT.client_) { // GRPC 客户端
      CLIENT.hostname_ = url || this.GRPC.API
      if (window.__GRPCWEB_DEVTOOLS__ && checkDebug(this)) window.__GRPCWEB_DEVTOOLS__([CLIENT])
    }
    return CLIENT
  },

  /**
   * GRPC 请求体 (会将 CORE.GRPC.COMMON_PARAM 合并进来)
   *
   * @param {Function} DtoClass 请求体类 (或已经生成过的 DTO)
   * @param {Object}   params   参数对象 (或已经生成过的 DTO)
   * @param {Array}    rule_1   规则其一 ()
   * @param {Array}    rule_2   规则其二 ()
   * @param {Array}    rule_n   规则其他 ()
   *
   * @returns {Object} DTO 请求体
   */
  //  规则示例 (三参跟二参同名，可简写为 1 ) (四参可传 handle = (dto, obj) => {} 进行其他处理)

  //  若为子对象，可为二参
  //  [ComClass, 'com_query']                 = DTO.setComQuery( grpcDTO(ComClass, params) )
  //  [BaseClass, 'content']                 = DTO.setContent( grpcDTO(BaseClass, params) )

  //  若为子对象，可为三参
  //  [BaseClass, 'content', 1]              = DTO.setContent( grpcDTO(BaseClass, params.content) )
  //  [BaseClass, 'content', 'child']        = DTO.setContent( grpcDTO(BaseClass, params.child) )
  //  [BaseClass, 'content', myObject]       = DTO.setContent( grpcDTO(BaseClass, myObject) )

  //  若为子列表，至少为三参
  //  [ChildClass, 'content', 1]          = DTO.setContentList([ grpcDTO(ChildClass, params.contentList[0]), ... ])
  //  [ChildClass, 'content', 'children'] = DTO.setContentList([ grpcDTO(ChildClass, params.children[0]), ... ])
  //  [ChildClass, 'content', myArray]    = DTO.setContentList([ grpcDTO(ChildClass, myArray[0]), ... ])
  grpcDTO(DtoClass, params, ...ruleList) {
    const DTO = IS.isClass(DtoClass) ? new DtoClass() : DtoClass
    const obj = params && IS.isFunction(params.toObject) ? params.toObject() : params || {}
    setDto(DTO, obj, this)

    ruleList.forEach(([ChildClass, key, x, handle]) => {
      let data = obj
      if (x) {
        if (IS.isArray(x) || IS.isJSON(x)) data = x
        else if (IS.isFunction(x.toObject)) data = x.toObject()
        else data = obj[x === 1 ? key : x]
      }

      const method = this.keyPre(key, 'set')
      if (!DTO[method]) throw new Error(`DTO.${method}() 不存在`)

      if (IS.isJSON(data)) setDtoChild(DTO, method, ChildClass, data, handle, this)
      else if (IS.isArray(data)) setDtoChildren(DTO, method, ChildClass, data, handle, this)
      else throw new Error(`param.${x === 1 ? key : x} 不能用于 DTO.${method}()`)
    })
    return DTO
  },

  /**
   * GRPC 成功
   *
   * @param {String} TIP 提示语
   *
   * @returns {String} 成功语句
   */
  grpcOK(TIP = '操作') {
    return `${transTIP(TIP)}：成功`
  },
}

// ============================== 内部方法 ============================== //

const transTIP = (TIP) => TIP.replace(/^【.*】 /, '')

/**
 * 转换方法
 *
 * @param {String} TIP    提示语
 * @param {Object} CLIENT 客户端
 * @param {String} method 方法名
 * @param {Object} DTO    请求体
 * @param {Object} CORE   内核库
 *
 * @returns {String} set 首字母大写的方法名
 */
const transMethod = (TIP, CLIENT, method, DTO, CORE) => {
  const name = CORE.key(method)
  if (CLIENT[name]) return name

  const params = DTO.toObject ? DTO.toObject() : CORE.json(DTO)
  const err = new Error(`${transTIP(TIP)}：CLIENT 没有 ${method} 方法`)
  CORE.logError(TIP, params, err)
  throw err
}

/**
 * 成功
 *
 * @param {String} TIP  提示语
 * @param {Object} DTO  请求体
 * @param {Object} RESP 响应体
 * @param {Object} CORE 内核库
 * @param {Number} time 时间戳
 *
 * @returns {Object} resp 响应体
 */
const success = (TIP, DTO, RESP = {}, CORE, time) => {
  const times = Date.now() - time
  const params = DTO.toObject ? DTO.toObject() : CORE.json(DTO)
  const resp = RESP.toObject ? RESP.toObject() : CORE.json(RESP)

  const { code, msg, err, tip } = resp.comResp || resp.comresp || resp

  if (code !== CORE.GRPC.SUCCESS) {
    CORE.logWarn(TIP, params, resp, times)

    CORE.GRPC.failResp && CORE.GRPC.failResp(resp, CORE) // 全局的失败响应

    const msgErr = `${transTIP(TIP)}：${tip || msg || err || '未知错误'}`
    throw new Error(msgErr)
  }

  if ('comquery' in resp && !resp.comquery) resp.comquery = { totalCount: 0 }
  if ('comQuery' in resp && !resp.comQuery) resp.comQuery = { totalCount: 0 }

  CORE.logInfo(TIP, params, resp, times)

  return resp
}

/**
 * 错误
 *
 * @param {String} TIP  提示语
 * @param {Object} DTO  请求体
 * @param {Error}  err  错误类
 * @param {Object} CORE 内核库
 * @param {Number} time 时间戳
 *
 * @returns {Error} error 错误类
 */
const error = (TIP, DTO, err, CORE, time) => {
  const params = DTO.toObject ? DTO.toObject() : CORE.json(DTO)
  const tip = transTIP(TIP)
  let msgErr = err.message || err.statusText || '异常'

  if (msgErr.includes(tip)) return err // success 中抛出的异常

  CORE.GRPC.catchError && CORE.GRPC.catchError(err, CORE) // 全局的捕获异常
  CORE.logError(TIP, params, err, Date.now() - time)

  if (err.statusText === 'Internal Server Error') msgErr = '网络异常'
  if (err.status === 404 || [2, 14].includes(err.code)) msgErr = '服务未启动'

  return new Error(`${tip}：${msgErr}`)
}

/**
 * 保存文件
 *
 * @param {String} TIP      提示语
 * @param {Object} DTO      请求体
 * @param {Object} resp     响应对象
 * @param {Object} CORE     内核库
 * @param {Number} time     时间戳
 * @param {String} fileName 文件名称
 */
const saveFile = (TIP, DTO, resp, CORE, time, fileName) => {
  const params = DTO.toObject ? DTO.toObject() : CORE.json(DTO)
  const name = fileName || resp.fileName || CORE.fileName()

  // ASCII 流
  const ascii = resp.filebytes || resp.fileBytes || resp.content
  if (!ascii) {
    CORE.logWarn(TIP, params, resp, Date.now() - time)
    const msgErr = `${transTIP(TIP)}：文件为空`
    throw new Error(msgErr)
  }

  // 二进制流
  const binaryStr = atob(ascii)
  let n = binaryStr.length
  const binary = new Uint8Array(n)
  while (n--) binary[n] = binaryStr.charCodeAt(n) // 隐形转换

  // 文件下载
  CORE.fileSave(binary, name)
}

/**
 * 能否设置
 *
 * @param {*} value 数据
 *
 * @returns {Boolean}
 */
const canSet = (value) => {
  if (!value) return true
  if (IS.isString(value) || IS.isNumber(value) || IS.isBoolean(value)) return true
  if (!IS.isArray(value)) return false
  return value.every((item) => canSet(item))
}

/**
 * 设置 DTO 本身
 *
 * @param {Object} DTO    数据对象
 * @param {Object} params 参数对象
 * @param {Object} CORE   内核库
 */
const setDto = (DTO, params, CORE) => {
  const setObj = (obj) => {
    Object.keys(obj).forEach((key) => {
      const method = CORE.keyPre(key, 'set')
      if (DTO[method] && canSet(obj[key])) DTO[method](obj[key])
    })
  }
  setObj(params || {})
  setObj(CORE.GRPC.COMMON_PARAM)
}

/**
 * 设置 DTO 子项
 *
 * @param {Object} DTO          数据对象
 * @param {String} method       设置方法
 * @param {Function} ChildClass 列项类
 * @param {Object} child        子项对象
 * @param {Function} handle     额外的处理方法，例 (childDto, child) => {}
 * @param {Object} CORE         内核库
 */
const setDtoChild = (DTO, method, ChildClass, child, handle, CORE) => {
  const childDto = IS.isClass(ChildClass) ? new ChildClass() : ChildClass
  setDto(childDto, child, CORE)
  IS.isFunction(handle) && handle(childDto, child) // 额外处理
  DTO[method](childDto)
}

/**
 * 设置子列表
 *
 * @param {Object} DTO          数据对象
 * @param {String} method       设置方法
 * @param {Function} ChildClass 列项类
 * @param {Array} children      子项列表
 * @param {Function} handle     额外的处理方法，例 (childDto, child) => {}
 * @param {Object} CORE         内核库
 */
const setDtoChildren = (DTO, method, ChildClass, children, handle, CORE) => {
  const childDtoList = children.map((child) => {
    const childDto = new ChildClass()
    setDto(childDto, child, CORE)
    IS.isFunction(handle) && handle(childDto, child) // 额外处理
    return childDto
  })
  DTO[method](childDtoList)
}
